package com.sky.templatehttpd;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Serving HTTP content throw a tcp socket.<br/>
 * Contains helper methods to ease custom http server creation.
 * TODO: Manage HEAD request
 */
public abstract class Server {

    /** Commons logger used for logging. */
    private static final Log LOG = LogFactory.getLog(Server.class);
    /** Port to bind. */
    private final int port;
    private Thread loopThread = null;
    private boolean stopping = false;
    private ServerSocket serverSocket = null;
    private final Object stopSync = new Object();

    /**
     * Create and initialize a new Server.
     * @param port Port to bind
     */
    public Server(int port) {
        this.port = port;
    }

    /**
     * Starts HTTP server.
     * @throws IOException  If the socket is already in use
     */
    public void start() throws IOException {

        if (isStarted()) {
            LOG.warn("Server is already started.");
            return;
        }

        LOG.debug("Creating a new server (" + hashCode() + ") thread on port " + port + ".");

        serverSocket = new ServerSocket(port);

        loopThread = new Thread(new WaitClients(this));
        loopThread.setDaemon(true);
        loopThread.start();
    }

    /** Stop HTTP server. */
    public void stop() {
        LOG.debug("Stopping http service (" + hashCode() + ") on port " + port + ".");

        if (!isStarted()) {
            LOG.warn("Server is not started.");
            return;
        }
        setStopping(true);
        closeServerSocket();
    }

    /** @return True is server is started and listening otherwise false */
    public boolean isStarted() {
        return loopThread != null && loopThread.isAlive();
    }

    /** @return True if server is currently stopping otherwise false */
    public boolean isStopping() {
        synchronized (stopSync) {
            return stopping;
        }
    }

    /** @return The server binding port */
    public int getPort() {
        return port;
    }

    /**
     * Called on client request. Must answer throw a Response object.
     * @param uri Percent-decoded URI without parameters (e.g. <code>/index.htm</code>)
     * @param method "GET", "POST" etc
     * @param params Parsed, percent decoded parameters from URI and, in case of POST, data
     * @param header Header entries, percent decoded
     * @return HTTP response, see class Response for details
     */
    abstract protected Response serve(String uri, String method, Properties header, Properties params);

    @Override
    protected void finalize() throws Throwable {
        if (isStarted()) {
            LOG.warn("Stopping server on finalize.");
            stop();
        }
        super.finalize();
    }

    private void onStopped() {
        closeServerSocket();
        synchronized (stopSync) {
            loopThread = null;
        }
        setStopping(false);
    }

    private void setStopping(boolean stopping) {
        synchronized (stopSync) {
            this.stopping = stopping;
        }
        synchronized (this) {
            notifyAll();
        }
    }

    private void closeServerSocket() {
        synchronized (this) {
            if (serverSocket != null) {
                if (!serverSocket.isClosed()) {
                    try {
                        serverSocket.close();
                    } catch (IOException ioex) {
                        LOG.warn("Ignored Exception while closing server socket", ioex);
                    }
                }
                serverSocket = null;
            }
        }
    }

    private final class WaitClients implements Runnable {

        private final Server server;

        WaitClients(Server server) {
            this.server = server;
        }

        @Override
        public void run() {
            LOG.debug("Starting http service on port " + port + ".");

            try {
                while (!isStopping()) {
                    if (serverSocket == null) {
                        break;
                    }
                    Socket socketRequest = serverSocket.accept();
                    new Session(server, socketRequest).process();
                }
            } catch (IOException ioex) {
                LOG.info("Stop serving requests: " + ioex.getMessage());
            } finally {
                onStopped();
                LOG.debug("Finished http service on port " + port + ".");
            }
        }
    }
}
